package homework2.NearestPoints;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

/**
 * Random generate points demo frame 
 * 
 * @author eDisOn 2009212634 lilh09@mails.tsinghua.edu.cn
 */
public class AutoNearestPointsFrame extends JInternalFrame
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 7045043378750408942L;

	/* Used to specify AutoNearestPointsFrame location and size */
	private static final int CASCADE_WIDTH = 10;
	private static final int LOCATION_X = 300;
	private static final int LOCATION_Y = 50;
	private static final int SIZE_X = 700;
	private static final int SIZE_Y = 500;
	private static final int R = 2; // radius of points

	private ArrayList<Point> points;
	private JScrollPane scrollPane;
	private JPanel paintPanel;
	private JPanel runPanel;
	private JLabel inputLabel;
	private JComboBox inputFiled;
	private JButton generateButton;
	private JButton runButton;
	private JButton runBFAButton;
	private int index = 0;

	// help with finding duplicate points
	HashMap<Integer, HashSet<Integer>> coordMap = new HashMap<Integer, HashSet<Integer>>();

	public AutoNearestPointsFrame(int initIndex)
	{
		super("Auto-Generated Nearest Points Frame", true, true, true, true);
		index = initIndex;
		init();
	}

	private void init()
	{
		points = new ArrayList<Point>();

		scrollPane = new JScrollPane(new JPanel(),
				JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		add(scrollPane, BorderLayout.CENTER);

		runPanel = new JPanel();
		runPanel.setLayout(new FlowLayout());
		runPanel.setBackground(Color.LIGHT_GRAY);

		inputLabel = new JLabel("Please input points amount:");

		inputFiled = new JComboBox();
		inputFiled.setEditable(true);
		inputFiled.addItem("10");
		inputFiled.addItem("100");
		inputFiled.addItem("1000");
		inputFiled.addItem("10000");
		inputFiled.addItem("100000");
		inputFiled.addItem("1000000");

		generateButton = new JButton("Generate");
		generateButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				try
				{
					int pointAmount = Integer.parseInt(inputFiled
							.getSelectedItem().toString());

					if (pointAmount < 2)
					{
						JOptionPane.showMessageDialog(
								AutoNearestPointsFrame.this,
								"You should specify two points at least!");
					}
					else
					{
						int bound = getBound(pointAmount);
						points = generatePoints(pointAmount, bound);

						paintPanel = getPanel(bound);

						if (scrollPane != null)
							AutoNearestPointsFrame.this.remove(scrollPane);

						scrollPane = new JScrollPane(paintPanel,
								JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
								JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

						AutoNearestPointsFrame.this.add(scrollPane,
								BorderLayout.CENTER);

						runButton.setEnabled(true);
						runBFAButton.setEnabled(true);
						generateButton.setEnabled(false);

						AutoNearestPointsFrame.this.updateUI();

						paintPanel.repaint();
					}
				}
				catch (NumberFormatException nfe)
				{
					JOptionPane.showMessageDialog(AutoNearestPointsFrame.this,
							"Points amount should be an integer");
				}
				catch (Exception ex)
				{
					ex.printStackTrace();
				}
			}
		});

		runButton = new JButton("RUN O(n*lg(n))");
		runButton.setEnabled(false);
		runButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				CalcNearestPoints calculator = new CalcNearestPoints();
				long start = System.currentTimeMillis();
				ArrayList<NearestPoints> nearestPointsArr = calculator
						.exec(points);
				long end = System.currentTimeMillis();

				generateButton.setEnabled(true);
				runButton.setEnabled(false);

				JOptionPane.showMessageDialog(AutoNearestPointsFrame.this,
						"Run time is : " + (end - start) + "ms\n"
								+ calculator.getResult(nearestPointsArr));
			}
		});

		runBFAButton = new JButton("RUN O(n^2)");
		runBFAButton.setEnabled(false);
		runBFAButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				BruteForceAlgorithm calculator = new BruteForceAlgorithm();
				long start = System.currentTimeMillis();
				ArrayList<NearestPoints> nearestPointsArr = calculator
						.exec(points);
				long end = System.currentTimeMillis();

				generateButton.setEnabled(true);
				runBFAButton.setEnabled(false);

				JOptionPane.showMessageDialog(AutoNearestPointsFrame.this,
						"Run time is : " + (end - start) + "ms\n"
								+ calculator.getResult(nearestPointsArr));
			}
		});

		runPanel.add(inputLabel);
		runPanel.add(inputFiled);
		runPanel.add(generateButton);
		runPanel.add(runButton);
		runPanel.add(runBFAButton);

		add(runPanel, BorderLayout.SOUTH);

		/* Set properties of this frame */
		setBounds(LOCATION_X + CASCADE_WIDTH * index, LOCATION_Y
				+ CASCADE_WIDTH * index, SIZE_X, SIZE_Y);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		setVisible(true);
	}

	private int getBound(int pointAmount)
	{
		int bound;

		if (pointAmount < 100)
		{
			bound = (int) Math.sqrt(100 * 100);
		}
		else if (pointAmount < 1000)
		{
			bound = (int) Math.sqrt(100 * 1000);
		}
		else if (pointAmount < 10000)
		{
			bound = (int) Math.sqrt(100 * 10000);
		}
		else if (pointAmount < 100000)
		{
			bound = (int) Math.sqrt(100 * 100000);
		}
		else if (pointAmount < 1000000)
		{
			bound = (int) Math.sqrt(100 * 1000000);
		}
		else
		{
			bound = 10 * 10000;
		}
		return bound;
	}

	private JPanel getPanel(int bound)
	{
		JPanel paintPanel = new JPanel()
		{
			public void paint(Graphics g)
			{
				super.paint(g);
				Graphics2D g2D = (Graphics2D) g;
				g2D.setColor(Color.DARK_GRAY);
				for (Point point : points)
				{
					int x = point.x > R ? point.x - R : 0;
					int y = point.y > R ? point.y - R : 0;

					g2D.fill(new Ellipse2D.Double(x, y, 2 * R + 1, 2 * R + 1));
				}
			}
		};
		Dimension d = new Dimension(bound, bound);
		paintPanel.setPreferredSize(d);

		return paintPanel;
	}

	private ArrayList<Point> generatePoints(int pointAmount, int bound)
	{
		points.clear();
		coordMap.clear();

		Random rand = new Random();
		for (int i = 0; i < pointAmount; i++)
		{
			int x, y;
			do
			{
				x = rand.nextInt(bound);
				y = rand.nextInt(bound);
			}
			while (exists(x, y));

			points.add(new Point(x, y));

			if (coordMap.containsKey(x))
			{
				coordMap.get(x).add(y);
			}
			else
			{
				HashSet<Integer> ySet = new HashSet<Integer>();
				ySet.add(y);
				coordMap.put(x, ySet);
			}
		}
		return points;
	}

	private boolean exists(int x, int y)
	{
		if (coordMap.containsKey(x))
		{
			if (coordMap.get(x).contains(y))
			{
				return true;
			}
		}
		return false;
	}
}